package com.inspur.edp.commonmodel.engine.core.data;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.inspur.edp.cef.core.data.AccessorCollection;
import com.inspur.edp.cef.core.data.RootEntityAccessor;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.entity.accessor.base.AccessorBase;
import com.inspur.edp.cef.entity.accessor.base.AccessorComparer;
import com.inspur.edp.cef.entity.accessor.base.IAccessor;
import com.inspur.edp.cef.entity.accessor.base.ReadonlyDataException;
import com.inspur.edp.cef.entity.accessor.dataType.ValueObjAccessor;
import com.inspur.edp.cef.entity.changeset.AddChangeDetail;
import com.inspur.edp.cef.entity.changeset.IChangeDetail;
import com.inspur.edp.cef.entity.changeset.InnerUtil;
import com.inspur.edp.cef.entity.changeset.ModifyChangeDetail;
import com.inspur.edp.cef.entity.changeset.ValueObjModifyChangeDetail;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IEntityDataCollection;
import com.inspur.edp.cef.entity.entity.IMultiLanguageAcc;
import com.inspur.edp.cef.entity.entity.IMultiLanguageData;
import com.inspur.edp.cef.entity.i18n.MultiLanguageInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.DataTypeResInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.EntityResInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefEntityResInfoImpl;
import com.inspur.edp.commonmodel.engine.api.data.IEngineEntityData;
import com.inspur.edp.commonmodel.engine.core.common.CMUtil;
import com.inspur.edp.commonmodel.engine.core.data.serializer.CommEngineDataSerializer;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.var;

@JsonSerialize(using = CommEngineDataSerializer.class)
public class EngineRootAccessor extends RootEntityAccessor  implements IEngineEntityData {

  @JSONField(serialize = false)
  protected final IGspCommonObject node;
  protected final CefEntityResInfoImpl resInfo;
  private final boolean isReadonly;
  protected HashMap<String, IEntityDataCollection> childs;
  protected Map<String, Object> nestedValues;

  @Deprecated
  public EngineRootAccessor(IGspCommonObject be, IEntityData inner) {
    this(be, inner, null);
  }
  public EngineRootAccessor(IGspCommonObject be, IEntityData inner, CefEntityResInfoImpl resInfo) {
    this(be, inner, resInfo, false);
  }

  public EngineRootAccessor(IGspCommonObject be, IEntityData inner, CefEntityResInfoImpl resInfo,
      boolean isReadonly) {
    super(inner);
    this.node = be;
    this.resInfo = resInfo;
    this.isReadonly = isReadonly;
    initialize();
  }

  @Override
  public String getNodeCode() {
    return node.getCode();
  }

  @Override
  protected HashMap<String, IEntityDataCollection> innerGetChilds() {
    return new HashMap<>(childs);
  }

  @Override
  protected ICefData innerCreateChild(String childCode) {
    Objects.requireNonNull(childCode, "childCode");

    IGspCommonObject childNode = CMUtil
        .checkNodeExists(node, childCode, getNodePredicate());
    EntityResInfo childResInfo = resInfo != null ? resInfo.getChildEntityResInfo(childNode.getCode()) : null;
    return new EngineChildAccessor(childNode, (IEntityData) getInnerData().createChild(childCode),
            (CefEntityResInfoImpl) childResInfo);
  }

  @Override
  protected IEntityDataCollection innerCreateAndSetChildCollection(String s) {
    IGspCommonObject child = CMUtil.checkNodeExists(node, s, getNodePredicate());
    CefEntityResInfoImpl childResInfo =
        resInfo != null ? (CefEntityResInfoImpl) resInfo.getChildEntityResInfo(child.getCode())
            : null;
    AccessorCollection collection = new AccessorCollection(null,
        childResInfo,
        () -> new EngineChildData(child, childResInfo),
        (data) -> new EngineChildAccessor(child, data, childResInfo));
    collection.setParent(this);
    childs.put(s, collection);
    return collection;
  }

  @Override
  public boolean getIsReadonly() {
    return isReadonly;
  }

  @Override
  protected Object innerGetValue(String labeldId) {
    Objects.requireNonNull(labeldId, "labeldId");

    var element = CMUtil.checkElementExists(node, labeldId, getElementPredicate());
    if (element.getIsUdt() || element.getObjectType()==GspElementObjectType.DynamicProp) {
      return nestedValues.get(element.getLabelID());
    } else {
      return getInnerData().getValue(element.getLabelID());
    }
  }

  @Override
  protected void innerSetValue(String labelId, Object value) {
    Objects.requireNonNull(labelId, "labelId");

    IGspCommonElement element = CMUtil.checkElementValue(node, labelId, value, getElementPredicate());
    if (element.getIsUdt() || element.getObjectType() == GspElementObjectType.DynamicProp) {
      DataAccUtil.throwSetUdtValue(element.getLabelID());
    }

    Object orgValue = getInnerData().getValue(element.getLabelID());
    super.raisePropertyChanging(element.getLabelID(), value, orgValue);
    if (!AccessorComparer.equals(orgValue, value)) {
      super.tryCopy();
      getInnerData().setValue(element.getLabelID(), value);
    }
  }

  @Override
  protected AccessorBase createNewObject() {
    return new EngineRootAccessor(node, null, resInfo, isReadonly);
  }

  @Override
  public void setID(String s) {
    throw new RuntimeException();
  }

  @Override
  public void copyCore(com.inspur.edp.cef.entity.accessor.base.AccessorBase accessor) {
    EngineRootAccessor result = (EngineRootAccessor) accessor;
    result.childs = new HashMap<>(childs.size(), 1);
    for (Map.Entry<String, IEntityDataCollection> pair : childs.entrySet()) {
      result.childs.put(pair.getKey(), ((AccessorCollection) pair.getValue()).copy(result));
    }
    if (nestedValues != null) {
      result.nestedValues = new HashMap<>(nestedValues.size(), 1);
      nestedValues.entrySet().forEach(pair -> {
        var nestedValue = (ValueObjAccessor) ((ICefData) pair.getValue()).copy();
        nestedValue.setBelongBuffer(result);
        result.nestedValues.put(pair.getKey(), nestedValue);
      });
    }
  }

  @Override
  protected ICefData innerCopySelf() {
    ICefData result = (ICefData) super.innerCopySelf();
    if (nestedValues != null) {
      nestedValues.entrySet()
          .forEach(pair -> result.setValue(pair.getKey(), ((ICefData) pair.getValue()).copySelf()));
    }
    return result;
  }

  private void initialize() {
    initializeNested();
    initializeChildCollection();
  }

  @Override
  protected void onInnerDataChange() {
    super.onInnerDataChange();
    initializeNested();
  }

  protected void initializeNested() {
    if (getInnerData() == null) {
      nestedValues = null;
      return;
    }

    nestedValues = new HashMap<>();
    CMUtil.accInitNestedValue(this, node, nestedValues, getElementPredicate(), resInfo);
  }

  protected void initializeChildCollection() {
    Stream<IGspCommonObject> stream = node.getContainChildObjects().stream();
    if (getNodePredicate() != null) {
      stream = stream.filter(getNodePredicate());
    }
    List<IGspCommonObject> childNodes = stream.collect(Collectors.toList());
    childs = new HashMap<>(childNodes.size(), 1);
    for (IGspCommonObject child : childNodes) {
      CefEntityResInfoImpl childResInfo =
          resInfo != null ? (CefEntityResInfoImpl) resInfo.getChildEntityResInfo(child.getCode())
              : null;
      AccessorCollection collection = new AccessorCollection(this,
          childResInfo,
          () -> new EngineChildData(child, childResInfo),
          (data) -> new EngineChildAccessor(child, data, childResInfo));
      childs.put(child.getCode(), collection);
    }
  }

  @Override
  protected DataTypeResInfo getResInfo() {
    return resInfo;
  }

  //#region overridable
  protected Predicate<IGspCommonField> getElementPredicate() {
    return null;
  }

  protected Predicate<IGspCommonObject> getNodePredicate() {
    return null;
  }

  @Override
  @JSONField(serialize = false)
  public final IGspCommonObject getNode() {
    return node;
  }
  //#endregion
}
